﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;

namespace TypeFinder 
{
    
    class TypeFinder 
    {
        private readonly string searchString;
        private readonly string typeToSearch;
        private readonly string searchDirectory;
        private readonly string searchIn;
        private readonly string accessibility;
        private readonly Type baseType;
        private bool cancelSearch;
        private readonly string matchCriteria;

        public TypeFinder(string searchString,
                          string typeToSearch,
                          string searchDirectory,
                          string searchIn,
                          string accessibility,
                          Type baseType,
                          string matchCriteria)
        {
            this.searchString = searchString;
            this.matchCriteria = matchCriteria;
            this.baseType = baseType;
            this.accessibility = accessibility;
            this.searchIn = searchIn;
            this.searchDirectory = searchDirectory;
            this.typeToSearch = typeToSearch;
        }

        public event EventHandler SearchCompleted;
        public event EventHandler<TypeFoundEventArgs> TypeFound;

        public string CurrentSearcedAssembly { get; set; }

        public void Cancel()
        {
            cancelSearch = true;
        }

        public void SearchAsync()
        {
            Action searchAction = Search;
            searchAction.BeginInvoke(null, null);
        }

        private void Search()
        {
            try
            {
                var extensions = new string[] {"*.dll", "*.exe"};
                foreach (string extension in extensions)
                {
                    var assemblies = Directory.EnumerateFiles(searchDirectory, extension, SearchOption.AllDirectories);

                    foreach (string assembly in assemblies)
                    {
                        if (cancelSearch) return;
                        CurrentSearcedAssembly = Path.GetFileName(assembly);
                        SearchInAssembly(assembly);
                    }
                }
                
                CurrentSearcedAssembly = null;
            }
            finally
            {
                if(null != SearchCompleted)
                {
                    SearchCompleted(this, EventArgs.Empty);
                }
            }
        }

        private void SearchInAssembly(string assemblyName)
        {
            try
            {
                string typename = searchString;
                
                Assembly assembly = Assembly.LoadFrom(assemblyName);
                Debug.WriteLine("Assembly loaded");
                var types = assembly.GetTypes();

                foreach (Type pp in types)
                {
                    if (cancelSearch) return;
                    if(null != baseType && !pp.IsSubclassOf(baseType) && !pp.GetInterfaces().Contains(baseType))
                    {
                        continue;   
                    }
                    if(!String.IsNullOrWhiteSpace(typename))
                    if (searchIn == "Type Name")
                    {
                        if (!MatchString(pp.Name, typename)) continue;
                    } 
                    else if (searchIn =="NameSpace")
                    {
                        if(!MatchString(pp.Namespace, typename)) continue;
                    }
                    else if (searchIn =="Type Name && NameSpace")
                    {
                        if(!MatchString(pp.FullName, typename)) continue;
                    }
                    else if (searchIn =="Assembly Name")
                    {
                        if(!MatchString(pp.AssemblyQualifiedName, typename)) continue;
                    }
                    else if (searchIn =="Assembly Path")
                    {
                        if(!MatchString(assemblyName, typename)) continue;
                    }
                    else if(!MatchString(pp.FullName, typename) && !MatchString(assemblyName, typename))
                    {
                        continue;
                    }

                    if (accessibility == "Public" && !pp.IsPublic) continue;
                    if (accessibility == "Internal" && pp.IsVisible) continue;

                    bool addItem = false;
                    if (typeToSearch == "Class" && pp.IsClass)
                    {
                        addItem = true;
                    } 
                    else if (typeToSearch == "Structures" && pp.IsValueType && !pp.IsEnum) 
                    {
                        addItem = true;
                    }
                    else if (typeToSearch == "Enums" && pp.IsValueType && pp.IsEnum) 
                    {
                        addItem = true;
                    } 
                    else if (typeToSearch == "Interfaces" && pp.IsInterface) 
                    {
                        addItem = true;
                    }
                    else if(typeToSearch == "All")
                    {
                        addItem = true;
                    }

                    if(addItem)
                    {
                        TypeInfo info = TypeInfo.Create(pp, assemblyName);
                        //context.Post(AddItem, info);
                        if(null != TypeFound)
                        {
                            TypeFound.BeginInvoke(this, new TypeFoundEventArgs(info), null, null);
                        }
                    }
                    else
                    {
                        Console.WriteLine(@"Ignoring: " + pp.FullName);
                    }
                    
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }

        bool MatchString(string line, string pattern)
        {
            switch (matchCriteria)
            {
                //case "Contains":
                //    return line.IndexOf(pattern, StringComparison.InvariantCultureIgnoreCase) > -1;
                case "Exact Match":
                    return String.Compare(line, pattern, StringComparison.OrdinalIgnoreCase) == 0;
                case "Starts With":
                    return line.StartsWith(pattern, StringComparison.OrdinalIgnoreCase);
                case "Ends With":
                    return line.EndsWith(pattern, StringComparison.OrdinalIgnoreCase);
                default:
                    return line.IndexOf(pattern, StringComparison.InvariantCultureIgnoreCase) > -1;
            }
        }
    }

    class TypeInfo
    {
        public Type Type { get; set; }
        public string AssemblyPath { get; private set; }
        public string TypeStr { get; private set; }

        internal static TypeInfo Create(Type type, string assemblyPath)
        {
            TypeInfo ti = new TypeInfo();

            if (type.IsClass)
                ti.TypeStr = @"Class";
            else if (type.IsEnum)
                ti.TypeStr = @"Enum";
            else if (type.IsInterface)
                ti.TypeStr = "Interface";
            else if (type.IsValueType)
                ti.TypeStr = @"Struct";
            ti.AssemblyPath = assemblyPath;
            ti.Type = type;            
            return ti;
        }
    }

    class TypeFoundEventArgs :EventArgs
    {
        public TypeInfo TypeInfo { get; private set; }

        internal TypeFoundEventArgs(TypeInfo typeInfo)
        {
            TypeInfo = typeInfo;
        }
    }
}

